package db;

import java.util.List;

import javax.persistence.EntityManager;


import org.apache.log4j.Logger;
import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Example;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Restrictions;
import org.hibernate.exception.JDBCConnectionException;

public class EntityWrapper<TYPE> {

  static Logger    LOG     = Logger.getLogger( EntityWrapper.class );
  private TxHandle tx;
  private static final boolean TRACE = "TRACE".equals( System.getProperty( "euca.log.exhaustive.db" ) );
  private static final String PersistenceUnit = "MEMC2";
  private static final String asc = "asc";
  private static final String desc = "desc";
  
  public EntityWrapper( ) {
    this( PersistenceUnit );
  }

  @SuppressWarnings( "unchecked" )
  public EntityWrapper( String persistenceContext ) {
    try {
      this.tx = new TxHandle( persistenceContext );
    } catch ( Throwable e ) {
      throw (RuntimeException) e ;
    }
  }

  @SuppressWarnings( "unchecked" )
  public List<TYPE> query( TYPE example ) {
    Example qbe = Example.create( example ).enableLike( MatchMode.EXACT );
    List<TYPE> resultList = ( List<TYPE> ) this.getSession( ).createCriteria( example.getClass( ) ).setCacheable( true ).add( qbe ).list( );
    return resultList;
  }
  
  public TYPE queryUnique( TYPE example ,int maxResult , String order, String orderType,Criterion ... criterion ) throws Exception {
	  
		List<TYPE> obj = ( List<TYPE> ) this.query(example ,maxResult ,order, orderType,criterion);
	    if ( obj.size( ) != 1 ) {
	        throw new Exception( "Error locating information .No record or more than 1 record return" );
	    }
	    return obj.get(0);
}
  
  @SuppressWarnings( "unchecked" )
  public List<TYPE> query( TYPE example ,int maxResult , String order, String orderType,Criterion ... criterion) {
	  Criteria criterion1;
	  criterion1 = this.getSession( ).createCriteria( example.getClass( ) ).setCacheable( true );
	  
	  if(criterion !=null)
		  for (Criterion criterion2 : criterion) {
			  criterion1 = criterion1.add(criterion2);
		  }
		  
	  
	  if(maxResult !=0)
		  criterion1 = criterion1.setMaxResults(maxResult);
	  
	  if(orderType !=null && order !=null)
		  if(orderType.equals(this.asc))
			  criterion1.addOrder(Order.asc(order));
		  else if (orderType.equals(this.desc))
			  criterion1.addOrder(Order.desc(order));
   
    List<TYPE> resultList = criterion1.list( );
    return resultList;
  }
  
  @SuppressWarnings( "unchecked" )
  public List<TYPE> query( String sql ) {
 
		Query query =  this.getSession( ).createQuery(sql);
		List<TYPE> obj = ( List<TYPE> ) query.list();
	    return obj;
  }
  
  @SuppressWarnings( "unchecked" )
  public TYPE queryUnique( String sql ) throws Exception {
 
		Query query =  this.getSession( ).createQuery(sql);
		List<TYPE> obj = ( List<TYPE> ) query.list();
	    if ( obj.size( ) != 1 ) {
	        throw new Exception( "Error locating information .No record or more than 1 record return" );
	    }
	    return obj.get(0);
	    
  }
  
  public Session getSession( ) {
	    return tx.getSession( );
 }

  public TYPE getUnique( TYPE example ) throws Exception {
    
    List<TYPE> res = this.query( example );
    if ( res.size( ) != 1 ) {
      throw new Exception( "Error locating information .No record or more than 1 record return" );
    }
    return res.get( 0 );
  }

  public void add( TYPE newObject ) {
    this.getEntityManager( ).persist( newObject );
  }

  public void merge( TYPE newObject ) {
    this.getEntityManager( ).merge( newObject );
  }

  public void mergeAndCommit( TYPE newObject ) {
    this.getEntityManager( ).merge( newObject );
    this.commit( );
  }

  public void delete( TYPE example, Object deleteKey ) {
	TYPE deleteObject = (TYPE) this.getEntityManager().getReference(example.getClass(), deleteKey);	
    this.getEntityManager( ).remove( deleteObject );
  }

  public void rollback( ) {
    try {
      this.tx.rollback( );
    } catch ( Throwable e ) {
      LOG.error(e);
    }
   }

  public void commit( ) {
    
    try {
      this.tx.commit( );
    } catch ( Throwable e ) {
      throw (RuntimeException) e ;
    }
  }

  public EntityManager getEntityManager( ) {
    return tx.getEntityManager( );
  }

  @SuppressWarnings( "unchecked" )
  public <NEWTYPE> EntityWrapper<NEWTYPE> recast( Class<NEWTYPE> c ) {
    return ( EntityWrapper<NEWTYPE> ) this;
  }

  public static StackTraceElement getMyStackTraceElement( ) {
    int i = 0;
    for ( StackTraceElement ste : Thread.currentThread( ).getStackTrace( ) ) {
      if ( i++ < 2 || ste.getClassName( ).matches( ".*EntityWrapper.*" ) 
          || ste.getClassName( ).matches( ".*TxHandle.*" )
          || ste.getMethodName( ).equals( "getEntityWrapper" ) ) {
        continue;
      } else {
        return ste;
      }
    }
    throw new RuntimeException( "BUG: Reached bottom of stack trace without finding any relevent frames." );
  }

}

